/**
 * Copyright 2016-2018 mobaas.com
 */
package com.mobaas.devops.node.docker;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.ArrayList;
import java.util.List;

import javax.net.ssl.SSLContext;

import org.apache.http.ssl.SSLContexts;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;

/**
 * 
 * @author billy zhang
 *
 */
public class DockerCertificate {

  public static final String DEFAULT_CA_CERT_NAME = "ca.pem";
  public static final String DEFAULT_CLIENT_CERT_NAME = "client-cert.pem";
  public static final String DEFAULT_CLIENT_KEY_NAME = "client-key.pem";

  private static final char[] KEY_STORE_PASSWORD = "zh3admin".toCharArray();

  private final SSLContext sslContext;

  public DockerCertificate(final String dockerCertPath) throws DockerCertificateException {
   
    try {

      final PrivateKey clientKey = readPrivateKey(Paths.get(dockerCertPath, DEFAULT_CLIENT_KEY_NAME));
      final List<Certificate> clientCerts = readCertificates(Paths.get(dockerCertPath, DEFAULT_CLIENT_CERT_NAME));

      final KeyStore keyStore = newKeyStore();
      keyStore.setKeyEntry("key", clientKey, KEY_STORE_PASSWORD,
              clientCerts.toArray(new Certificate[clientCerts.size()]));

      final List<Certificate> caCerts = readCertificates(Paths.get(dockerCertPath, DEFAULT_CA_CERT_NAME));

      final KeyStore trustStore = newKeyStore();
      for (Certificate caCert : caCerts) {
        X509Certificate crt = (X509Certificate) caCert;
        String alias = crt.getSubjectX500Principal().getName();
        trustStore.setCertificateEntry(alias, caCert);
      }

      this.sslContext = new DefaultSslContextFactory()
          .newSslContext(keyStore, KEY_STORE_PASSWORD, trustStore);

    } catch (DockerCertificateException e) {
    	throw e;
    } catch (CertificateException
        | IOException
        | NoSuchAlgorithmException
        | InvalidKeySpecException
        | KeyStoreException
        | UnrecoverableKeyException
        | KeyManagementException e) {
      
      throw new DockerCertificateException(e);
    }
  }

  private KeyStore newKeyStore() throws CertificateException, NoSuchAlgorithmException,
      IOException, KeyStoreException {
    final KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
    keyStore.load(null);
    return keyStore;
  }

  private PrivateKey readPrivateKey(Path file) throws IOException, InvalidKeySpecException,
      NoSuchAlgorithmException, DockerCertificateException {
    try (BufferedReader reader = Files.newBufferedReader(file, Charset.defaultCharset());
         PEMParser pemParser = new PEMParser(reader)) {

      final Object readObject = pemParser.readObject();

      if (readObject instanceof PEMKeyPair) {
        PEMKeyPair clientKeyPair = (PEMKeyPair) readObject;
        return generatePrivateKey(clientKeyPair.getPrivateKeyInfo());
      } else if (readObject instanceof PrivateKeyInfo) {
        return generatePrivateKey((PrivateKeyInfo) readObject);
      }

      throw new DockerCertificateException("Can not generate private key from file: "
          + file.toString());
    }
  }

  private static PrivateKey generatePrivateKey(PrivateKeyInfo privateKeyInfo) throws IOException,
          InvalidKeySpecException, NoSuchAlgorithmException {
    final PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(privateKeyInfo.getEncoded());
    final KeyFactory kf = KeyFactory.getInstance("RSA");
    return kf.generatePrivate(spec);
  }

  private List<Certificate> readCertificates(Path file) throws CertificateException, IOException {
    try (InputStream inputStream = Files.newInputStream(file)) {
      final CertificateFactory cf = CertificateFactory.getInstance("X.509");
      return new ArrayList<>(cf.generateCertificates(inputStream));
    }
  }

  public SSLContext sslContext() {
    return this.sslContext;
  }

  /*
  public HostnameVerifier hostnameVerifier() {
    return NoopHostnameVerifier.INSTANCE;
  }*/

  public interface SslContextFactory {
    SSLContext newSslContext(KeyStore keyStore, char[] keyPassword, KeyStore trustStore)
        throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException,
        KeyManagementException;
  }

  private static class DefaultSslContextFactory implements SslContextFactory {
    @Override
    public SSLContext newSslContext(KeyStore keyStore, char[] keyPassword, KeyStore trustStore)
        throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException,
        KeyManagementException {
      return SSLContexts.custom()
              .loadTrustMaterial(trustStore, null)
              .loadKeyMaterial(keyStore, keyPassword)
              .build();
    }
  }

}
